package autohotkeyide;

import java.awt.Color;
import java.awt.Font;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Element;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
import javax.swing.text.TabSet;
import javax.swing.text.TabStop;
import javax.swing.text.Utilities;

/**
 * @author sad
 */
public class IDETextField extends JTextPane {

    private StyledDocument document;
    private final RegExpImageFinder finder = new RegExpImageFinder();
    private int lastOffset;
    private boolean disableReplacingImages = false;

    public IDETextField() {
        document = new DefaultStyledDocument() {
            @Override
            public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
                if ("\t".equals(str)) {
                    str = "    ";
                }
                super.insertString(offs, str, a);
            }
        };

        setFont(new Font("Monospaced", 0, 13));
        setDocument(document);
        StyleContext sc = StyleContext.getDefaultStyleContext();
        TabSet tabs = new TabSet(new TabStop[]{new TabStop(20)});
        AttributeSet paraSet = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.TabSet, tabs);
        setParagraphAttributes(paraSet, false);
        document.putProperty(DefaultEditorKit.EndOfLineStringProperty, System.getProperty("line.separator"));
        document.addDocumentListener(new DocumentListener() {
            @Override
            public void insertUpdate(DocumentEvent e) {
                setModified(true);
                if (!disableReplacingImages) {
                    documentInserted(e.getOffset(), e.getLength());
                }
                if (e.getLength() == 1) {
                    try {
                        String change = getText(e.getOffset(), e.getLength());
                        if ("\n".equals(change)) {
                            SwingUtilities.invokeLater(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        newRow();
                                    } catch (BadLocationException ex) {
                                        Logger.getLogger(IDETextField.class.getName()).log(Level.SEVERE, null, ex);
                                    }
                                }
                            });
                        }
                    } catch (BadLocationException ex) {
                        Logger.getLogger(IDETextField.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                setModified(true);
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
            }
        });

        finder.setOnFindEvent(new RegExpImageFinder.FindEvent() {
            @Override
            public void onFind(Matcher matcher) {
                int start = matcher.start();
                int end = matcher.end();
                String path = matcher.group(1);
                SimpleAttributeSet attrs = new SimpleAttributeSet();
                if (new File(path).exists()) {
                    ImageViewer image = new ImageViewer(new File(path));
                    StyleConstants.setComponent(attrs, image);
                } else {
                    StyleConstants.setForeground(attrs, Color.red);
                }

                document.setCharacterAttributes(lastOffset + start, end - start, attrs, true);
            }
        });

        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (KeyEvent.VK_HOME == e.getKeyCode()) {
                    onHome(e.getModifiers() % KeyEvent.SHIFT_DOWN_MASK != 0);
                    e.consume();
                }
            }
        });
    }

    private void onHome(final boolean shift) {
        try {
            int newCaretPos = 0;
            int row = getSelectedRow() - 1;
            String line = getLine(row);
            int caretPos = getCaretPositionInRow();
            int textBoundPos = getIdentationOfLine(line).length();
            if (caretPos == 0) {
                newCaretPos = textBoundPos;
            } else if (caretPos <= textBoundPos) {
                newCaretPos = 0;
            } else if (caretPos > textBoundPos) {
                newCaretPos = textBoundPos;
            }

            final int carPos = getGlobalCaretPosition(row, newCaretPos);
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    if (shift) {
                        moveCaretPosition(carPos);
                    } else {
                        setCaretPosition(carPos);
                    }
                }
            });
        } catch (BadLocationException ex) {
            Logger.getLogger(IDETextField.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private String getIdentationOfLine(String text) {
        StringBuilder sb = new StringBuilder(100);
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c == ' ' || c == '\t') {
                sb.append(c);
            } else {
                break;
            }
        }

        return sb.toString();
    }

    private String getLine(int line) {
        return getText().split("\\n")[line];
    }

    private void newRow() throws BadLocationException {
        int row = getSelectedRow();
        int rowBeforeIndex = row - 2;
        if (rowBeforeIndex < 0) {
            return;
        }
        final String text = getLine(rowBeforeIndex);
        if (text != null) {
            document.insertString(getCaretPosition(), getIdentationOfLine(text), null);
        }
    }

    public String[] getRowsBeforeAndSelectedRows() {
        String[] lines = null;
        try {
            lines = getText(0, getCaretPosition()).split("\\r?\\n");
        } catch (BadLocationException ex) {
            Logger.getLogger(IDETextField.class.getName()).log(Level.SEVERE, null, ex);
        }
        return lines;
    }

    public String getStringBeforeCaretAndStartOfRow() {
        try {
            int offs = Utilities.getRowStart(this, getCaretPosition()) - 1;
            return getText(offs + 1, getCaretPosition() - offs - 1);
        } catch (BadLocationException ex) {
            Logger.getLogger(IDETextField.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    public int getSelectedRow() {
        return getRowIndex(getCaretPosition());
    }

    public int getCaretPositionInRow() {
        try {
            int caret = getCaretPosition();
            int start = Utilities.getRowStart(this, caret);
            return caret - start;
        } catch (BadLocationException ex) {
            Logger.getLogger(IDETextField.class.getName()).log(Level.SEVERE, null, ex);
        }
        return 0;
    }

    private int getLineStartOffset(int line) throws BadLocationException {
        Element map = document.getDefaultRootElement();
        if (line < 0) {
            throw new BadLocationException("Negative line", -1);
        } else if (line >= map.getElementCount()) {
            throw new BadLocationException("No such line", document.getLength() + 1);
        } else {
            Element lineElem = map.getElement(line);
            return lineElem.getStartOffset();
        }
    }

    public int getGlobalCaretPosition(int row, int caretPosInLine) throws BadLocationException {
        int offset = getLineStartOffset(row);
        return offset + caretPosInLine;
    }

    public int getRowIndex(int pos) {
        int rn = (pos == 0) ? 1 : 0;
        try {
            int offs = pos;
            while (offs > 0) {
                offs = Utilities.getRowStart(this, offs) - 1;
                rn++;
            }
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
        return rn;
    }

    public int getColumnIndex(int pos) {
        try {
            return pos - Utilities.getRowStart(this, pos) + 1;
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
        return -1;
    }

    public boolean isDisableReplacingImages() {
        return disableReplacingImages;
    }

    public void setDisableReplacingImages(boolean disableReplacingImages) {
        this.disableReplacingImages = disableReplacingImages;
    }

    public void removeAllText() {
        setText("");
        clearModified();
    }

    public void removeText(int start, int length) {
        try {
            setModified(true);
            document.remove(start, length);
        } catch (BadLocationException ex) {
            Logger.getLogger(IDETextField.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void insertText(int start, String text) {
        try {
            document.insertString(start, text, null);
        } catch (BadLocationException ex) {
            Logger.getLogger(IDETextField.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void replaceImages(String text, int offset) {
        lastOffset = offset;
        finder.trySearch(text);
    }

    private void documentInserted(final int start, int length) {
        try {
            final String changedText = document.getText(start, length);
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    replaceImages(changedText, start);
                }
            });
        } catch (BadLocationException ex) {
            Logger.getLogger(IDETextField.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    private boolean modified = false;

    public void clearModified() {
        modified = false;
    }

    public void setModified(boolean modified) {
        this.modified = modified;
    }

    public boolean isModified() {
        return modified;
    }
}
